/*
 * TODO now:
 * - see todo file...
 *
 * REVISED MOVEMENT
 * - if calcNewPosition knows both current & intended direction, it can calculate a delta distance
 *   and check if that distance crosses an intersection; if so, it can allow the intended dir and calculate
 *   the new position accordingly (by going around a corner on the intersection)
 *   -> solves pixelperfectness as well as accidentally missing intersection margins
 *
 * TODO/GENERAL NOTES later:
 * - merge direction enums?
 * - change PngSequencePlayer frame advancement to work like Player in order to have stable coupling of speed to time (i.e., use floats + roundf())
 * - replace dirList scanner for player images by a callback (somewhere around ofxFileTransferConnection.cpp:70 using shared_from_this?)
 * - for drawing in ViewController, allow the following situations:
 *   + internal(if available): switch between gui+miniview OR fullscreenview
 *   + external (if available): fullview OPTIONALLY WITH gui
 *   take into account: int/ext display, (deformation mesh))
 * - specify dimensions for drawing texture? (should be fetched/set from ViewController, may be necessary if video will not be resized to 1400*1050)
 *
 * NOTES:
 * - express player speed changes (for powerups etc.) in factors of base speeds (so changing the base is possible without breaking balances)
 * - the approach to loading image sequences in pngSequencePlayer is a bit naive
 */
#include <string>
#include <vector>
#include "LevelUpApp.h"
#include "Settings.h"
#include "Utils.h"

const string LevelUpApp::CONFIG_FILE = "config.ini";
const string LevelUpApp::GUI_FONT_FILE = "fonts/monaco.ttf";
const int LevelUpApp::GUI_FONT_SIZE = 8;


LevelUpApp::LevelUpApp( ofxFenster* fenster_ )
: debugDrawingEnabled( false ), s( Settings::getInstance() ), screenModeChanged( 3 ),
  fenster( fenster_ ), game( *this ), gameController( *this ),
  viewController( *this ), audioController( *this )
{}

//--------------------------------------------------------------
void LevelUpApp::setup() {
	ofSetLogLevel( OF_LOG_NOTICE );
	
	s.loadFromFile( CONFIG_FILE );
	
	int maxFPS = ofToInt( *s.getValue( "fps_max", "0" ) );
	ofSetFrameRate( maxFPS );
	ofLog( OF_LOG_NOTICE, "max framerate set to: %i fps", maxFPS );
	appFrameRate = ofGetFrameRate();
	showAppFrameRate = false;
	
	game.setup();
	gameController.setup();
	viewController.setup();
	audioController.setup();
	oscDataSender.setup();
	
#ifdef IGNORE_EXISTING_PHOTOBOOTH_FILES
	remoteIO.setup( false );
#else
	remoteIO.setup( true );
#endif
	ofAddListener( remoteIO.onNewPhotoboothFile, &gameController, &GameController::onNewPhotoboothFile );
	
	
	/* SETUP GUI */
	
	ofxControlPanel::setBackgroundColor( simpleColor( 30, 30, 60, 200 ) );
	ofxControlPanel::setTextColor( simpleColor( 240, 50, 50, 255 ) );
	
	gui.addPanel( "main config", 4, false );
	gui.setWhichPanel( 0 );
	gui.setWhichColumn( 0 );
	
	gui.addToggle( "debugDrawing", "debug-drawing", false );
	ofAddListener( gui.createEventGroup( "debug-drawing" ), this, &LevelUpApp::guiEventCallback );
	
	std::vector<guiVariablePointer> guiVars;
	guiVars.push_back( guiVariablePointer( "app fps", &appFrameRate, GUI_VAR_INT ) );
	
	gui.addVariableLister( "app variables", guiVars );
	gui.addChartPlotter( "fps chart", guiStatVarPointer( "app fps", &appFrameRate, GUI_VAR_INT, true ), 200, 100, 200, 0, 80 );
	
	//--window
	bool bWindowFullscreen = parseBool( *s.getValue( "window_fullscreen", "false" ) );
	if ( bWindowFullscreen ) {
		ofSetWindowPosition( ofToInt( *s.getValue( "window_set_position", "0" ) ), 0 );
		ofSetFullscreen( true );
		gui.hide();
	} else {
		ofSetFullscreen( false );
	}
	
	
	/* ofxFenster */
	
	/* FENSTER USAGE (if settings should be available in main.cpp, maybe init them there?):
	 * - gui font can only be loaded into one of both contexts (unless new version with 007 is used)
	 * - FIXME: gui only draws correctly when 2nd win is exactly as high as 1st one (why??)
	 *
	 * - Possible configurations:
	 *   * with 2 screens: (int+beamer) -> gui with mini-gameview only on int screen since gui on beamer makes no sense
	 *   * with 1 int screen -> gui overlayed on gameview
	 *   * with 1 ext screen -> same as 1 int screen?
	 */
	
	if ( fenster ) {
		fenster->setFPS( maxFPS, maxFPS );
		fenster->toContext();
		
		gui.loadFont( GUI_FONT_FILE, GUI_FONT_SIZE );
		gui.setup( "Level U.P.", 0, 0, fenster->getWidth(), fenster->getHeight() * 0.66f );
		
		fenster->toMainContext();
	} else {
		gui.loadFont( GUI_FONT_FILE, GUI_FONT_SIZE );
		gui.setup( "Level U.P.", 0, 0, ofGetWidth(), ofGetHeight() * 0.66f );
	}
	
	inputController.setup();
	ofAddListener( inputController.onPlayerInput, &gameController, &GameController::onPlayerInput );
}

//--------------------------------------------------------------
void LevelUpApp::update() {
	appFrameRate = ofGetFrameRate();
	
	remoteIO.update();
	inputController.update();
	gameController.update();
	viewController.update();
	audioController.update();
	gui.update();
	
	//NOTE: the counter construction is necessary to overcome delayed updating of screen dimensions by oF
	if ( screenModeChanged > 0 ) {
		screenModeChanged--;
		if ( screenModeChanged == 0 ) viewController.setViewportSize( ofGetWidth(), ofGetHeight() );
		//screenModeChanged = false;
	}
}

//--------------------------------------------------------------
void LevelUpApp::draw() {
	viewController.draw();
	
	ofSetColor( 255, 255, 255 );
	gui.draw();
	
	if ( gui.hidden && showAppFrameRate ) {
		char fpsString[100];
		snprintf( fpsString, sizeof( fpsString ), "FPS: %i", appFrameRate );
		ofDrawBitmapString( fpsString, 10, 10 );
	}
}

const GameModel& LevelUpApp::getGame() const { return game; }
GameModel& LevelUpApp::getGame() { return game; }
const GameController& LevelUpApp::getGameController() const { return gameController; }
GameController& LevelUpApp::getGameController() { return gameController; }
const ViewController& LevelUpApp::getViewController() const { return viewController; }
AudioController& LevelUpApp::getAudioController() { return audioController; }
const OscDataSender& LevelUpApp::getOscDataSender() const { return oscDataSender; }
OscDataSender& LevelUpApp::getOscDataSender() { return oscDataSender; }


//--------------------------------------------------------------
void LevelUpApp::keyPressed( int key ) {
	inputController.keyPressed( key );
	
	switch ( key ) {
		case 'f': case 'F':
			toggleFullscreen();
			break;
		case 'e': case 'E':
			setFullscreen( true );
			break;
		case 'p': case 'P':
			game.setPaused( ! game.isPaused() );
			break;
		case 'g': case 'G':
			gui.hidden = ! gui.hidden;
			break;
		case '!': case '1':
			audioController.restartMusic();
			break;
		case '@': case '2':
			debugDrawingEnabled = ! debugDrawingEnabled;
			break;
		case 'm': case 'M':
			audioController.setMusicMuted( ! audioController.isMusicMuted() );
			break;
		case '?':
			showAppFrameRate = ! showAppFrameRate;
			break;
	}
}

//--------------------------------------------------------------
void LevelUpApp::keyReleased( int key ) {}

//--------------------------------------------------------------
void LevelUpApp::mouseMoved( int x, int y ) {}

//--------------------------------------------------------------
void LevelUpApp::mouseDragged( int x, int y, int button ) {
	gui.mouseDragged( x, y, button );
}

//--------------------------------------------------------------
void LevelUpApp::mousePressed( int x, int y, int button ) {
	gui.mousePressed( x, y, button );
}

//--------------------------------------------------------------
void LevelUpApp::mouseReleased( int x, int y, int button ) {}

//--------------------------------------------------------------
//NOTE: WARNING: when switching to fullscreen, this function receives incorrect dimensions!
void LevelUpApp::windowResized( int w, int h ) {
//	static const float fourThirdsAngle = atan2f( 3.0f, 4.0f );
//	
//	int nw = w, nh = h;
//	
//	//TODO: only restrict dimensions if not in fullscreen mode
//	if ( true ) {
//		int originDist = ofDist( 0.0f, 0.0f, w, h );
//		nw = cosf( fourThirdsAngle ) * originDist;
//		nh = sinf( fourThirdsAngle ) * originDist;
//		
//		//TODO: restrict to screen dimensions
//		
////		int wnw = w;
////		int wnh = w * ( 3.0f / 4.0f );
////		int hnh = h;
////		int hnw = h * ( 4.0f / 3.0f );
////		
////		int sw = ofGetWidth(), sh = ofGetHeight();
////		if ( wnw > sw ) { wnw = sw; wnh = w * ( 3.0f / 4.0f ); }
////		if ( hnh > sh ) { hnh = sh; hnw = h * ( 4.0f / 3.0f ); }
////		
////		//TODO: assign either wnw&wnh or hnw&hnh to nw&nh, depending on which set is closest to the original;
////		//however this won't work since the window can be resized freely anyway until dragging is stopped
////		//TODO: neh...maybe window area change should be minimized...
////		nw = ( wnw + wnh - w + h < hnw + hnh - w + h ) ? wnw : hnw;
////		nh = ( wnw + wnh - w + h < hnw + hnh - w + h ) ? wnh : hnh;
//		
//		//ofSetWindowShape( nw, nh );
//	}
//	
//	viewController.setViewportSize( nw, nh );
}

void LevelUpApp::guiEventCallback( guiCallbackData& data ) {
	if ( data.isElement( "debug-drawing" ) ) {
		ofLog( OF_LOG_WARNING, "received toggle change" ); //TEMP
		debugDrawingEnabled = data.getInt( 0 ) ? true : false;
	}
}

void LevelUpApp::setFullscreen( bool fs ) {
	screenModeChanged = 3;
	ofSetFullscreen( fs );
}

void LevelUpApp::toggleFullscreen() {
	screenModeChanged = 3;
	ofToggleFullscreen();
}


/************************
 * ofxFenster FUNCTIONS *
 ************************/

void LevelUpApp::fensterDraw() {
	ofSetColor( 0xffffff );
	gui.draw();
}

void LevelUpApp::fensterUpdate() {
}

void LevelUpApp::fensterKeyPressed( int key ) {
}

void LevelUpApp::fensterKeyReleased( int key ) {
}

void LevelUpApp::fensterMouseMoved( int x, int y ) {
}

//NOTE: drag is not yet implemented
void LevelUpApp::fensterMouseDragged( int x, int y, int button ) {
}

void LevelUpApp::fensterMousePressed( int x, int y, int button ) {
}

void LevelUpApp::fensterMouseReleased( int x, int y, int button ) {
}

void LevelUpApp::fensterWindowResized( int w, int h ) {
}
